<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>js this的详细解释</title>
</head>
<body>
    <script>
        
        // this的指向是一个复杂的问题，主要取决于函数的调用方式。以下是一些常见的情况：
        // 1. 全局作用域中的this指向全局对象（在浏览器中是window）
        console.log(this); // 在浏览器中输出window对象
        
        // 2. 在函数中调用this，指向全局对象（在严格模式下是undefined）
        // 为什么严格模式指向undefined？
        // 因为在严格模式下，this不会指向全局对象，而是保持为undefined。
        // 这有助于避免一些常见的错误，例如在函数中意外地修改全局对象的属性。
        // 这使得代码更加安全和可预测。
        // 这也是为什么在严格模式下，this的值是undefined，而不是全局对象的原因。
        function showThisStrict() {
            'use strict'; // 启用严格模式
            console.log(this); // 输出undefined
        }
        showThisStrict(); // 调用函数，输出this

        function showThis() {
            console.log(this); // 在浏览器中输出window对象
        }

        showThis(); // 调用函数，输出this

        // 3. 在对象的方法中调用this，指向该对象
        const obj = {
            name: 'Alice',
            greet: function() {
                console.log(this.name); // 输出'Alice'
            }
        };
        obj.greet(); // 调用对象的方法，输出this.name

        // 4. 在构造函数中调用this，指向新创建的实例对象
        function Person(name) {
            this.name = name; // 将name属性赋值给实例对象
        }
        const person = new Person('Bob'); // 创建新实例
        console.log(person.name); // 输出'Bob'

        // 5. 使用call、apply或bind方法改变this的指向
        function greet() {
            console.log(this.name); // 输出name属性
        }
        const user = {
            name: 'Charlie'
        };
        greet.call(user); // 使用call方法，this指向user对象，输出'Charlie'
        greet.apply(user); // 使用apply方法，this指向user对象，输出'Charlie'
        const boundGreet = greet.bind(user); // 使用bind方法，创建一个新的函数，this指向user对象
        boundGreet(); // 调用绑定后的函数，输出'Charlie'

        // 6. 箭头函数中的this指向外部作用域的this
        const arrowFunction = () => {
            console.log(this); // 输出外部作用域的this
        };
        arrowFunction(); // 调用箭头函数，输出外部作用域的this

        // 7. 在事件处理函数中，this指向触发事件的元素
        const button = document.createElement('button');    
        button.innerText = 'Click me';
        button.addEventListener('click', function() {
            console.log(this); // 输出button元素
        });
        document.body.appendChild(button); // 将按钮添加到文档中        
        
        // 8. 在setTimeout或setInterval中，this指向全局对象（在严格模式下是undefined）
        setTimeout(function() {
            console.log(this); // 在浏览器中输出window对象
        }, 1000);
        
        function todoSomething() {
            setTimeout(function() {
                console.log(8, this); // 在浏览器中输出window对象
            }, 1000);
        }

        todoSomething(); // 调用函数，输出this
        
        // 9. 在setTimeout或setInterval中使用箭头函数，this指向外部作用域的this
        setTimeout(() => {
            console.log(this); // 输出外部作用域的this
        }, 1000);

        function todoSomething2() {
            function fun() {
                setTimeout(function () {
                    console.log(9, this); // 输出外部作用域的this 
                }, 1000);
            } // 内部函数

            fun(); // 调用内部函数
            console.log(9.1, this)
        }

        todoSomething2(); // 调用函数，输出this

        // 10. 在DOM事件处理程序中，this指向触发事件的元素
        const button2 = document.createElement('button');
        button2.innerText = 'Click me';
        button2.addEventListener('click', function() {
            console.log(10, this); // 输出button2元素
        });
        document.body.appendChild(button2); // 将按钮添加到文档中

        // 11. 在DOM事件处理程序中使用箭头函数，this指向外部作用域的this
        const button3 = document.createElement('button');
        button3.innerText = 'Click me';

        button3.addEventListener('click', () => {
            console.log(11, this); // 输出外部作用域的this
        });
        document.body.appendChild(button3); // 将按钮添加到文档中

        // 12. 在setTimeout或setInterval中使用箭头函数，this指向外部作用域的this
        setTimeout(() => {
            console.log(12, this); // 输出外部作用域的this
        }, 1000);

        // 13. 在setTimeout或setInterval中使用bind方法，this指向绑定的对象
        const user2 = {
            name: 'Eve'
        };

        setTimeout(function() {
            console.log(13, this.name); // 输出'Eve'
        }.bind(user2), 1000); // 使用bind方法，this指向user2对象

        // 14. 在setTimeout或setInterval中使用call方法，this指向绑定的对象
        const user3 = {
            name: 'Frank'
        };  

        setTimeout(function() {
            console.log(14, this.name); // 输出'Frank'
        }.call(user3), 1000); // 使用call方法，this指向user3对象

        // 15. 在setTimeout或setInterval中使用apply方法，this指向绑定的对象
        const user4 = {
            name: 'Grace'
        };

        setTimeout(function() {
            console.log(15, this.name); // 输出'Grace'
        }.apply(user4), 1000); // 使用apply方法，this指向user4对象


        // 16. 在class中，this指向实例对象
        class Animal {
            constructor(name) {
                this.name = name; // 将name属性赋值给实例对象
            }

            speak() {
                console.log(`${this.name} makes a noise.`); // 输出动物的叫声
            }
        }
        const dog = new Animal('Dog'); // 创建新实例
        dog.speak(); // 调用实例方法，输出'狗叫声'

        // 17. 在class中使用箭头函数，this指向实例对象
        class Cat {
            constructor(name) {
                this.name = name; // 将name属性赋值给实例对象
            }

            speak = () => {
                console.log(`${this.name} meows.`); // 输出猫的叫声
            }
        }
        const cat = new Cat('Cat'); // 创建新实例
        cat.speak(); // 调用实例方法，输出'猫叫声'

        // 18. 在继承中，this指向子类实例对象
        class Animal2 {
            constructor(name) {
                this.name = name; // 将name属性赋值给实例对象
            }

            speak() {
                console.log(`${this.name} makes a noise.`); // 输出动物的叫声
            }
        }

        class Dog extends Animal2 {
            speak() {
                console.log(`${this.name} barks.`); // 输出狗的叫声
            }
        }
        const dog2 = new Dog('Dog'); // 创建新实例
        dog2.speak(); // 调用子类方法，输出'狗叫声'
        
        // 19. 在继承中使用箭头函数，this指向子类实例对象

        class Cat2 extends Animal2 {
            speak = () => {
                console.log(`${this.name} meows.`); // 输出猫的叫声
            }
        }

        const cat2 = new Cat2('Cat'); // 创建新实例
        cat2.speak(); // 调用子类方法，输出'猫叫声'


        // 20.
        const options = {
            name: 'John',
            func: {
                getName() {
                    console.log('getName', this.name); 
                },
                getName2: () => {
                    console.log('getName2', this.name); 
                }, 
                getName3: function () {
                    console.log('getName3', this.name); 
                },
                // getName4: function() {
                //     console.log('getName4', this.name); 
                // }.bind(options) // 使用bind方法，this指向options对象
            },
            func2: {
                // getName: options.func.getName.bind(options), // 报错 options还没有初始化完成
                // getName3: options.func.getName3.bind(options), // 报错
            }
        }
        options.func.getName(); // 调用方法，输出undefined
        options.func.getName2(); // 调用方法，输
        options.func.getName3(); // 调用方法，输出undefined
        options.func.getName4(); // 
        // options.func2.getName(); // 
        options.func2.getName3(); // 
    </script>
</body>
</html>